﻿namespace DomainServices.Tools
{
    using System;
    using System.Collections.Generic;
    using System.ComponentModel.DataAnnotations;
    using System.Linq;
    using System.Reflection;
    using System.ServiceModel.DomainServices;

    internal class StandardCustomAttributeBuilder : ICustomAttributeBuilder
    {
        private List<PropertyMap> BuildPropertyMaps(Attribute attribute)
        {
            List<PropertyMap> list = new List<PropertyMap>();
            foreach (PropertyInfo info in attribute.GetType().GetProperties())
            {
                string a = this.MapProperty(info, attribute);
                if (a != null)
                {
                    PropertyInfo property = null;
                    if (string.Equals(a, info.Name, StringComparison.Ordinal))
                    {
                        property = info;
                    }
                    else
                    {
                        property = attribute.GetType().GetProperty(a);
                    }
                    if ((property != null) && CanPropertyBeSetDeclaratively(property))
                    {
                        list.Add(new PropertyMap(info, property));
                    }
                }
            }
            return list;
        }

        private static bool CanPropertyBeSetDeclaratively(PropertyInfo property)
        {
            Type propertyType = property.PropertyType;
            if (propertyType.IsArray && propertyType.HasElementType)
            {
                propertyType = propertyType.GetElementType();
            }
            if ((!(propertyType == typeof(Type)) && !(propertyType == typeof(object))) && !System.ServiceModel.DomainServices.TypeUtility.IsPredefinedSimpleType(propertyType))
            {
                return false;
            }
            return true;
        }

        private static bool CanValueBeAssignedToType(Type type, object value)
        {
            if (value == null)
            {
                return (!type.IsValueType || (type.IsGenericType && (type.GetGenericTypeDefinition() == typeof(Nullable<>))));
            }
            Type c = value.GetType();
            return type.IsAssignableFrom(c);
        }

        private static object DefaultInstanceForType(Type t)
        {
            if (!t.IsValueType)
            {
                return null;
            }
            return Activator.CreateInstance(t);
        }

        private static ParameterInfo[] FindBestConstructor(ICollection<PropertyMap> propertyMaps, IDictionary<string, object> currentValues, Type attributeType)
        {
            ConstructorInfo[] constructors = attributeType.GetConstructors();
            if (propertyMaps.Count > 0)
            {
                foreach (ConstructorInfo info in constructors)
                {
                    ParameterInfo[] parameters = info.GetParameters();
                    if (ParametersAcceptAllProperties(parameters, propertyMaps, currentValues))
                    {
                        return parameters;
                    }
                }
                return null;
            }
            ParameterInfo[] infoArray3 = null;
            foreach (ConstructorInfo info2 in constructors)
            {
                ParameterInfo[] infoArray4 = info2.GetParameters();
                if (infoArray4.Length == 0)
                {
                    return infoArray4;
                }
                if (((infoArray4.Length == 1) && (currentValues.Count == 1)) && CanValueBeAssignedToType(infoArray4[0].ParameterType, currentValues.Values.Single<object>()))
                {
                    infoArray3 = infoArray4;
                }
                else if (infoArray3 == null)
                {
                    infoArray3 = infoArray4;
                }
            }
            return infoArray3;
        }

        public virtual AttributeDeclaration GetAttributeDeclaration(Attribute attribute)
        {
            if (attribute == null)
            {
                throw new ArgumentNullException("attribute");
            }
            Type attributeType = attribute.GetType();
            AttributeDeclaration declaration = new AttributeDeclaration(attributeType);
            List<PropertyMap> propertyMaps = this.BuildPropertyMaps(attribute);
            Dictionary<string, object> propertyValues = GetPropertyValues(propertyMaps, attribute);
            Dictionary<string, object> defaultValues = GetDefaultPropertyValues(propertyMaps, attribute, propertyValues);
            List<PropertyMap> list2 = GetNonDefaultProperties(propertyMaps, propertyValues, defaultValues);
            ParameterInfo[] infoArray = FindBestConstructor(GetUnsettableProperties(list2), propertyValues, attributeType);
            if (infoArray == null)
            {
                return null;
            }
            foreach (ParameterInfo info in infoArray)
            {
                PropertyMap item = null;
                foreach (PropertyMap map2 in propertyMaps)
                {
                    PropertyInfo setter = map2.Setter;
                    if (setter.Name.Equals(info.Name, StringComparison.OrdinalIgnoreCase) && CanValueBeAssignedToType(info.ParameterType, propertyValues[setter.Name]))
                    {
                        item = map2;
                        break;
                    }
                }
                object obj2 = (item != null) ? propertyValues[item.Getter.Name] : DefaultInstanceForType(info.ParameterType);
                declaration.ConstructorArguments.Add(obj2);
                if (item != null)
                {
                    list2.Remove(item);
                }
            }
            list2.Sort((x, y) => string.Compare(x.Setter.Name, y.Setter.Name, StringComparison.Ordinal));
            foreach (PropertyMap map3 in list2)
            {
                declaration.NamedParameters.Add(map3.Setter.Name, propertyValues[map3.Getter.Name]);
            }
            return declaration;
        }

        private static Dictionary<string, object> GetDefaultPropertyValues(IEnumerable<PropertyMap> propertyMaps, Attribute attribute, Dictionary<string, object> currentValues)
        {
            Type type = attribute.GetType();
            Attribute attribute2 = null;
            ConstructorInfo constructor = type.GetConstructor(new Type[0]);
            if (((constructor != null) && constructor.IsPublic) && !type.IsAbstract)
            {
                attribute2 = Activator.CreateInstance(type) as Attribute;
            }
            if (attribute2 != null)
            {
                return GetPropertyValues(propertyMaps, attribute2);
            }
            Dictionary<string, object> dictionary = new Dictionary<string, object>();
            foreach (PropertyMap map in propertyMaps)
            {
                PropertyInfo getter = map.Getter;
                object obj2 = currentValues[getter.Name];
                Type t = (obj2 == null) ? getter.PropertyType : obj2.GetType();
                object obj3 = DefaultInstanceForType(t);
                dictionary[getter.Name] = obj3;
            }
            return dictionary;
        }

        private static List<PropertyMap> GetNonDefaultProperties(IEnumerable<PropertyMap> propertyMaps, Dictionary<string, object> currentValues, Dictionary<string, object> defaultValues)
        {
            List<PropertyMap> list = new List<PropertyMap>();
            foreach (PropertyMap map in propertyMaps)
            {
                PropertyInfo getter = map.Getter;
                object objA = currentValues[getter.Name];
                object objB = defaultValues[getter.Name];
                if (!object.Equals(objA, objB))
                {
                    list.Add(map);
                }
            }
            return list;
        }

        private static Dictionary<string, object> GetPropertyValues(IEnumerable<PropertyMap> propertyMaps, Attribute attribute)
        {
            Dictionary<string, object> dictionary = new Dictionary<string, object>();
            foreach (PropertyMap map in propertyMaps)
            {
                MethodInfo method;
                PropertyInfo getter = map.Getter;
                object obj2 = null;
                if ((attribute is DisplayAttribute) && ((getter.Name == "AutoGenerateField") || (getter.Name == "AutoGenerateFilter")))
                {
                    method = attribute.GetType().GetMethod("Get" + getter.Name);
                    if ((method != null) && method.IsPublic)
                    {
                        obj2 = method.Invoke(attribute, null);
                    }
                }
                else
                {
                    method = getter.GetGetMethod();
                    if ((method != null) && method.IsPublic)
                    {
                        try
                        {
                            obj2 = getter.GetValue(attribute, null);
                        }
                        catch (TargetInvocationException exception)
                        {
                            throw new AttributeBuilderException(exception.InnerException, attribute.GetType(), getter.Name);
                        }
                    }
                }
                dictionary[getter.Name] = obj2;
            }
            return dictionary;
        }

        private static List<PropertyMap> GetUnsettableProperties(IEnumerable<PropertyMap> propertyMaps)
        {
            List<PropertyMap> list = new List<PropertyMap>();
            foreach (PropertyMap map in propertyMaps)
            {
                MethodInfo setMethod = map.Setter.GetSetMethod();
                if ((setMethod == null) || !setMethod.IsPublic)
                {
                    list.Add(map);
                }
            }
            return list;
        }

        protected virtual string MapProperty(PropertyInfo propertyInfo, Attribute attribute)
        {
            string name = propertyInfo.Name;
            if (!name.Equals("TypeId", StringComparison.Ordinal))
            {
                return name;
            }
            return null;
        }

        private static bool ParametersAcceptAllProperties(ParameterInfo[] parameters, ICollection<PropertyMap> propertyMaps, IDictionary<string, object> currentValues)
        {
            foreach (PropertyMap map in propertyMaps)
            {
                PropertyInfo setter = map.Setter;
                bool flag = false;
                foreach (ParameterInfo info2 in parameters)
                {
                    if (setter.Name.Equals(info2.Name, StringComparison.OrdinalIgnoreCase) && CanValueBeAssignedToType(info2.ParameterType, currentValues[setter.Name]))
                    {
                        flag = true;
                        break;
                    }
                }
                if (!flag)
                {
                    return (((propertyMaps.Count == 1) && (parameters.Length == 1)) && CanValueBeAssignedToType(parameters[0].ParameterType, currentValues[propertyMaps.First<PropertyMap>().Setter.Name]));
                }
            }
            return true;
        }

        internal class PropertyMap
        {
            private PropertyInfo _getter;
            private PropertyInfo _setter;

            public PropertyMap(PropertyInfo getter, PropertyInfo setter)
            {
                this._getter = getter;
                this._setter = setter;
            }

            public PropertyInfo Getter
            {
                get
                {
                    return this._getter;
                }
            }

            public PropertyInfo Setter
            {
                get
                {
                    return this._setter;
                }
            }
        }
    }
}

